{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Part 1: Data ingestion\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This demo showcases financial fraud prevention using the MLRun feature store to define complex features that help identify fraud. Fraud prevention specifically is a challenge because it requires processing raw transaction and events in real-time, and \n",
    "being able to quickly respond and block transactions before they occur.\n",
    "\n",
    "```{admonition} Note\n",
    "This demo works with the online feature store, which is currently not part of the Open Source default deployment.\n",
    "```\n",
    "\n",
    "To address this, you create a development pipeline and a production pipeline. Both pipelines share the same feature \n",
    "engineering and model code, but serve data very differently. Furthermore, you automate the data and model monitoring \n",
    "process, identify drift and trigger retraining in a CI/CD pipeline. This process is described in the diagram below:\n",
    "\n",
    "![Feature store demo diagram - fraud prevention](../../_static/images/feature_store_demo_diagram.png)\n",
    "\n",
    "By the end of this tutorial you’ll learn how to:\n",
    "\n",
    "- Create an ingestion pipeline for each data source.\n",
    "- Define preprocessing, aggregation and validation of the pipeline.\n",
    "- Run the pipeline locally within the notebook.\n",
    "- Launch a real-time function to ingest live data.\n",
    "- Schedule a cron to run the task when needed."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The raw data is described as follows:\n",
    "\n",
    "| TRANSACTIONS                                                                    || &#x2551; |USER EVENTS                                                                           || \n",
    "|-----------------|----------------------------------------------------------------|----------|-----------------|----------------------------------------------------------------|\n",
    "| **age**         | age group value 0-6. Some values are marked as U for unknown   | &#x2551; | **source**      | The party/entity related to the event                          |\n",
    "| **gender**      | A character to define the gender                               | &#x2551; | **event**       | event, such as login or password change                        |\n",
    "| **zipcodeOri**  | ZIP code of the person originating the transaction             | &#x2551; | **timestamp**   | The date and time of the event                                 |\n",
    "| **zipMerchant** | ZIP code of the merchant receiving the transaction             | &#x2551; |                 |                                                                |\n",
    "| **category**    | category of the transaction (e.g., transportation, food, etc.) | &#x2551; |                 |                                                                |\n",
    "| **amount**      | the total amount of the transaction                            | &#x2551; |                 |                                                                |\n",
    "| **fraud**       | whether the transaction is fraudulent                          | &#x2551; |                 |                                                                |\n",
    "| **timestamp**   | the date and time in which the transaction took place          | &#x2551; |                 |                                                                |\n",
    "| **source**      | the ID of the party/entity performing the transaction          | &#x2551; |                 |                                                                |\n",
    "| **target**      | the ID of the party/entity receiving the transaction           | &#x2551; |                 |                                                                |\n",
    "| **device**      | the device ID used to perform the transaction                  | &#x2551; |                 |                                                                |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This notebook introduces how to **Ingest** different data sources to the **Feature Store**.\n",
    "\n",
    "The following FeatureSets are created:\n",
    "- **Transactions**: Monetary transactions between a source and a target.\n",
    "- **Events**: Account events such as account login or a password change.\n",
    "- **Label**: Fraud label for the data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Both server & client are aligned (1.7.0rc40).\n"
     ]
    }
   ],
   "source": [
    "!/User/align_mlrun.sh"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "project_name = \"fraud-demo\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "> 2024-09-12 06:38:52,635 [info] Project loaded successfully: {\"project_name\":\"fraud-demo-jill\"}\n"
     ]
    }
   ],
   "source": [
    "import mlrun\n",
    "\n",
    "# Initialize the MLRun project object\n",
    "project = mlrun.get_or_create_project(project_name, context=\"./\", user_project=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 1 - Fetch, process and ingest the datasets"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1.1 - Transactions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Transactions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "tags": [
     "hide-cell"
    ]
   },
   "outputs": [],
   "source": [
    "# Helper functions to adjust the timestamps of our data\n",
    "# while keeping the order of the selected events and\n",
    "# the relative distance from one event to the other\n",
    "\n",
    "\n",
    "def date_adjustment(sample, data_max, new_max, old_data_period, new_data_period):\n",
    "    \"\"\"\n",
    "    Adjust a specific sample's date according to the original and new time periods\n",
    "    \"\"\"\n",
    "    sample_dates_scale = (data_max - sample) / old_data_period\n",
    "    sample_delta = new_data_period * sample_dates_scale\n",
    "    new_sample_ts = new_max - sample_delta\n",
    "    return new_sample_ts\n",
    "\n",
    "\n",
    "def adjust_data_timespan(\n",
    "    dataframe, timestamp_col=\"timestamp\", new_period=\"2d\", new_max_date_str=\"now\"\n",
    "):\n",
    "    \"\"\"\n",
    "    Adjust the dataframe timestamps to the new time period\n",
    "    \"\"\"\n",
    "    # Calculate old time period\n",
    "    data_min = dataframe.timestamp.min()\n",
    "    data_max = dataframe.timestamp.max()\n",
    "    old_data_period = data_max - data_min\n",
    "\n",
    "    # Set new time period\n",
    "    new_time_period = pd.Timedelta(new_period)\n",
    "    new_max = pd.Timestamp(new_max_date_str)\n",
    "    new_min = new_max - new_time_period\n",
    "    new_data_period = new_max - new_min\n",
    "\n",
    "    # Apply the timestamp change\n",
    "    df = dataframe.copy()\n",
    "    df[timestamp_col] = df[timestamp_col].apply(\n",
    "        lambda x: date_adjustment(\n",
    "            x, data_max, new_max, old_data_period, new_data_period\n",
    "        )\n",
    "    )\n",
    "    return df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>step</th>\n",
       "      <th>age</th>\n",
       "      <th>gender</th>\n",
       "      <th>zipcodeOri</th>\n",
       "      <th>zipMerchant</th>\n",
       "      <th>category</th>\n",
       "      <th>amount</th>\n",
       "      <th>fraud</th>\n",
       "      <th>timestamp</th>\n",
       "      <th>source</th>\n",
       "      <th>target</th>\n",
       "      <th>device</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>274633</th>\n",
       "      <td>91</td>\n",
       "      <td>5</td>\n",
       "      <td>F</td>\n",
       "      <td>28007</td>\n",
       "      <td>28007</td>\n",
       "      <td>es_transportation</td>\n",
       "      <td>26.92</td>\n",
       "      <td>0</td>\n",
       "      <td>2024-09-10 06:39:33.730952000</td>\n",
       "      <td>C1022153336</td>\n",
       "      <td>M1823072687</td>\n",
       "      <td>33832bb8607545df97632a7ab02d69c4</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>286902</th>\n",
       "      <td>94</td>\n",
       "      <td>2</td>\n",
       "      <td>M</td>\n",
       "      <td>28007</td>\n",
       "      <td>28007</td>\n",
       "      <td>es_transportation</td>\n",
       "      <td>48.22</td>\n",
       "      <td>0</td>\n",
       "      <td>2024-09-10 06:39:52.024577913</td>\n",
       "      <td>C1006176917</td>\n",
       "      <td>M348934600</td>\n",
       "      <td>fadd829c49e74ffa86c8da3be75ada53</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>416998</th>\n",
       "      <td>131</td>\n",
       "      <td>3</td>\n",
       "      <td>M</td>\n",
       "      <td>28007</td>\n",
       "      <td>28007</td>\n",
       "      <td>es_transportation</td>\n",
       "      <td>17.56</td>\n",
       "      <td>0</td>\n",
       "      <td>2024-09-10 06:39:57.131747939</td>\n",
       "      <td>C1010936270</td>\n",
       "      <td>M348934600</td>\n",
       "      <td>58d0422a50bc40c89d2b4977b2f1beea</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "        step age gender  zipcodeOri  zipMerchant           category  amount  \\\n",
       "274633    91   5      F       28007        28007  es_transportation   26.92   \n",
       "286902    94   2      M       28007        28007  es_transportation   48.22   \n",
       "416998   131   3      M       28007        28007  es_transportation   17.56   \n",
       "\n",
       "        fraud                     timestamp       source       target  \\\n",
       "274633      0 2024-09-10 06:39:33.730952000  C1022153336  M1823072687   \n",
       "286902      0 2024-09-10 06:39:52.024577913  C1006176917   M348934600   \n",
       "416998      0 2024-09-10 06:39:57.131747939  C1010936270   M348934600   \n",
       "\n",
       "                                  device  \n",
       "274633  33832bb8607545df97632a7ab02d69c4  \n",
       "286902  fadd829c49e74ffa86c8da3be75ada53  \n",
       "416998  58d0422a50bc40c89d2b4977b2f1beea  "
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import pandas as pd\n",
    "\n",
    "# Fetch the transactions dataset from the server\n",
    "transactions_data = pd.read_csv(\n",
    "    \"https://s3.wasabisys.com/iguazio/data/fraud-demo-mlrun-fs-docs/data.csv\",\n",
    "    parse_dates=[\"timestamp\"],\n",
    ")\n",
    "\n",
    "# use only first 50k\n",
    "transactions_data = transactions_data.sort_values(by=\"source\", axis=0)[:10000]\n",
    "\n",
    "# Adjust the samples timestamp for the past 2 days\n",
    "transactions_data = adjust_data_timespan(transactions_data, new_period=\"2d\")\n",
    "\n",
    "# Sorting after adjusting timestamps\n",
    "transactions_data = transactions_data.sort_values(by=\"timestamp\", axis=0)\n",
    "\n",
    "# Preview\n",
    "transactions_data.head(3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Transactions - create a feature set and preprocessing pipeline\n",
    "Create the feature set (data pipeline) definition for the **credit transaction processing** that describes the \n",
    "offline/online data transformations and aggregations.<br>\n",
    "The feature store automatically adds an offline `parquet` target and an online `NoSQL` target by using `set_targets()`.\n",
    "\n",
    "The data pipeline consists of:\n",
    "\n",
    "* **Extracting** the data components (hour, day of week)\n",
    "* **Mapping** the age values\n",
    "* **One hot encoding** for the transaction category and the gender\n",
    "* **Aggregating** the amount (avg, sum, count, max over 2/12/24 hour time windows)\n",
    "* **Aggregating** the transactions per category (over 14 days time windows)\n",
    "* **Writing** the results to **offline** (Parquet) and **online** (NoSQL) targets "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import MLRun's Feature Store\n",
    "import mlrun.feature_store as fstore\n",
    "from mlrun.feature_store.steps import OneHotEncoder, MapValues, DateExtractor"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define the transactions FeatureSet\n",
    "transaction_set = fstore.FeatureSet(\n",
    "    \"transactions\",\n",
    "    entities=[fstore.Entity(\"source\")],\n",
    "    timestamp_key=\"timestamp\",\n",
    "    description=\"transactions feature set\",\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 2.43.0 (0)\n",
       " -->\n",
       "<!-- Title: mlrun&#45;flow Pages: 1 -->\n",
       "<svg width=\"907pt\" height=\"98pt\"\n",
       " viewBox=\"0.00 0.00 907.45 98.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 94)\">\n",
       "<title>mlrun&#45;flow</title>\n",
       "<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-94 903.45,-94 903.45,4 -4,4\"/>\n",
       "<!-- _start -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>_start</title>\n",
       "<polygon fill=\"lightgrey\" stroke=\"black\" points=\"38.55,-27.05 40.7,-27.15 42.83,-27.3 44.92,-27.49 46.98,-27.74 48.99,-28.03 50.95,-28.36 52.84,-28.75 54.66,-29.18 56.4,-29.65 58.06,-30.16 59.63,-30.71 61.11,-31.31 62.49,-31.94 63.76,-32.61 64.93,-33.31 65.99,-34.04 66.93,-34.8 67.77,-35.59 68.48,-36.41 69.09,-37.25 69.58,-38.11 69.95,-38.99 70.21,-39.89 70.36,-40.8 70.4,-41.72 70.33,-42.65 70.16,-43.59 69.89,-44.53 69.53,-45.47 69.07,-46.41 68.52,-47.35 67.89,-48.28 67.18,-49.2 66.4,-50.11 65.55,-51.01 64.63,-51.89 63.65,-52.75 62.62,-53.59 61.53,-54.41 60.4,-55.2 59.23,-55.96 58.02,-56.69 56.78,-57.39 55.5,-58.06 54.2,-58.69 52.88,-59.29 51.53,-59.84 50.17,-60.35 48.79,-60.82 47.4,-61.25 46,-61.64 44.59,-61.97 43.17,-62.26 41.75,-62.51 40.32,-62.7 38.89,-62.85 37.45,-62.95 36.02,-63 34.58,-63 33.15,-62.95 31.71,-62.85 30.28,-62.7 28.85,-62.51 27.43,-62.26 26.01,-61.97 24.6,-61.64 23.2,-61.25 21.81,-60.82 20.43,-60.35 19.07,-59.84 17.72,-59.29 16.4,-58.69 15.1,-58.06 13.82,-57.39 12.58,-56.69 11.37,-55.96 10.2,-55.2 9.07,-54.41 7.98,-53.59 6.95,-52.75 5.97,-51.89 5.05,-51.01 4.2,-50.11 3.42,-49.2 2.71,-48.28 2.08,-47.35 1.53,-46.41 1.07,-45.47 0.71,-44.53 0.44,-43.59 0.27,-42.65 0.2,-41.72 0.24,-40.8 0.39,-39.89 0.65,-38.99 1.02,-38.11 1.51,-37.25 2.11,-36.41 2.83,-35.59 3.66,-34.8 4.61,-34.04 5.67,-33.31 6.84,-32.61 8.11,-31.94 9.49,-31.31 10.97,-30.71 12.54,-30.16 14.2,-29.65 15.94,-29.18 17.76,-28.75 19.65,-28.36 21.61,-28.03 23.62,-27.74 25.68,-27.49 27.77,-27.3 29.9,-27.15 32.05,-27.05 34.22,-27 36.38,-27 38.55,-27.05\"/>\n",
       "<text text-anchor=\"middle\" x=\"35.3\" y=\"-41.3\" font-family=\"Times,serif\" font-size=\"14.00\">start</text>\n",
       "</g>\n",
       "<!-- DateExtractor -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>DateExtractor</title>\n",
       "<ellipse fill=\"none\" stroke=\"black\" cx=\"183.94\" cy=\"-45\" rx=\"77.19\" ry=\"18\"/>\n",
       "<text text-anchor=\"middle\" x=\"183.94\" y=\"-41.3\" font-family=\"Times,serif\" font-size=\"14.00\">DateExtractor</text>\n",
       "</g>\n",
       "<!-- _start&#45;&gt;DateExtractor -->\n",
       "<g id=\"edge1\" class=\"edge\">\n",
       "<title>_start&#45;&gt;DateExtractor</title>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M69.94,-45C78.02,-45 87.01,-45 96.29,-45\"/>\n",
       "<polygon fill=\"black\" stroke=\"black\" points=\"96.46,-48.5 106.45,-45 96.45,-41.5 96.46,-48.5\"/>\n",
       "</g>\n",
       "<!-- MapValues -->\n",
       "<g id=\"node3\" class=\"node\">\n",
       "<title>MapValues</title>\n",
       "<ellipse fill=\"none\" stroke=\"black\" cx=\"359.03\" cy=\"-45\" rx=\"61.99\" ry=\"18\"/>\n",
       "<text text-anchor=\"middle\" x=\"359.03\" y=\"-41.3\" font-family=\"Times,serif\" font-size=\"14.00\">MapValues</text>\n",
       "</g>\n",
       "<!-- DateExtractor&#45;&gt;MapValues -->\n",
       "<g id=\"edge2\" class=\"edge\">\n",
       "<title>DateExtractor&#45;&gt;MapValues</title>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M261.36,-45C269.86,-45 278.49,-45 286.92,-45\"/>\n",
       "<polygon fill=\"black\" stroke=\"black\" points=\"287.08,-48.5 297.08,-45 287.08,-41.5 287.08,-48.5\"/>\n",
       "</g>\n",
       "<!-- OneHotEncoder -->\n",
       "<g id=\"node4\" class=\"node\">\n",
       "<title>OneHotEncoder</title>\n",
       "<ellipse fill=\"none\" stroke=\"black\" cx=\"541.92\" cy=\"-45\" rx=\"85.29\" ry=\"18\"/>\n",
       "<text text-anchor=\"middle\" x=\"541.92\" y=\"-41.3\" font-family=\"Times,serif\" font-size=\"14.00\">OneHotEncoder</text>\n",
       "</g>\n",
       "<!-- MapValues&#45;&gt;OneHotEncoder -->\n",
       "<g id=\"edge3\" class=\"edge\">\n",
       "<title>MapValues&#45;&gt;OneHotEncoder</title>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M421.02,-45C429.27,-45 437.9,-45 446.57,-45\"/>\n",
       "<polygon fill=\"black\" stroke=\"black\" points=\"446.75,-48.5 456.75,-45 446.75,-41.5 446.75,-48.5\"/>\n",
       "</g>\n",
       "<!-- Aggregates -->\n",
       "<g id=\"node5\" class=\"node\">\n",
       "<title>Aggregates</title>\n",
       "<ellipse fill=\"none\" stroke=\"black\" cx=\"726.75\" cy=\"-45\" rx=\"63.89\" ry=\"18\"/>\n",
       "<text text-anchor=\"middle\" x=\"726.75\" y=\"-41.3\" font-family=\"Times,serif\" font-size=\"14.00\">Aggregates</text>\n",
       "</g>\n",
       "<!-- OneHotEncoder&#45;&gt;Aggregates -->\n",
       "<g id=\"edge4\" class=\"edge\">\n",
       "<title>OneHotEncoder&#45;&gt;Aggregates</title>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M627.11,-45C635.71,-45 644.39,-45 652.85,-45\"/>\n",
       "<polygon fill=\"black\" stroke=\"black\" points=\"653.04,-48.5 663.04,-45 653.04,-41.5 653.04,-48.5\"/>\n",
       "</g>\n",
       "<!-- parquet/parquet -->\n",
       "<g id=\"node6\" class=\"node\">\n",
       "<title>parquet/parquet</title>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M899.45,-86.73C899.45,-88.53 883.09,-90 862.95,-90 842.81,-90 826.45,-88.53 826.45,-86.73 826.45,-86.73 826.45,-57.27 826.45,-57.27 826.45,-55.47 842.81,-54 862.95,-54 883.09,-54 899.45,-55.47 899.45,-57.27 899.45,-57.27 899.45,-86.73 899.45,-86.73\"/>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M899.45,-86.73C899.45,-84.92 883.09,-83.45 862.95,-83.45 842.81,-83.45 826.45,-84.92 826.45,-86.73\"/>\n",
       "<text text-anchor=\"middle\" x=\"862.95\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">parquet</text>\n",
       "</g>\n",
       "<!-- Aggregates&#45;&gt;parquet/parquet -->\n",
       "<g id=\"edge5\" class=\"edge\">\n",
       "<title>Aggregates&#45;&gt;parquet/parquet</title>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M779.25,-55.36C791.37,-57.8 804.21,-60.38 816.06,-62.77\"/>\n",
       "<polygon fill=\"black\" stroke=\"black\" points=\"815.69,-66.26 826.18,-64.8 817.07,-59.4 815.69,-66.26\"/>\n",
       "</g>\n",
       "<!-- nosql/nosql -->\n",
       "<g id=\"node7\" class=\"node\">\n",
       "<title>nosql/nosql</title>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M890.45,-32.73C890.45,-34.53 878.12,-36 862.95,-36 847.77,-36 835.45,-34.53 835.45,-32.73 835.45,-32.73 835.45,-3.27 835.45,-3.27 835.45,-1.47 847.77,0 862.95,0 878.12,0 890.45,-1.47 890.45,-3.27 890.45,-3.27 890.45,-32.73 890.45,-32.73\"/>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M890.45,-32.73C890.45,-30.92 878.12,-29.45 862.95,-29.45 847.77,-29.45 835.45,-30.92 835.45,-32.73\"/>\n",
       "<text text-anchor=\"middle\" x=\"862.95\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">nosql</text>\n",
       "</g>\n",
       "<!-- Aggregates&#45;&gt;nosql/nosql -->\n",
       "<g id=\"edge6\" class=\"edge\">\n",
       "<title>Aggregates&#45;&gt;nosql/nosql</title>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M779.25,-34.64C794.6,-31.55 811.1,-28.23 825.29,-25.38\"/>\n",
       "<polygon fill=\"black\" stroke=\"black\" points=\"826.05,-28.79 835.16,-23.39 824.67,-21.93 826.05,-28.79\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "<graphviz.graphs.Digraph at 0x7f69466cc490>"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Define and add value mapping\n",
    "main_categories = [\n",
    "    \"es_transportation\",\n",
    "    \"es_health\",\n",
    "    \"es_otherservices\",\n",
    "    \"es_food\",\n",
    "    \"es_hotelservices\",\n",
    "    \"es_barsandrestaurants\",\n",
    "    \"es_tech\",\n",
    "    \"es_sportsandtoys\",\n",
    "    \"es_wellnessandbeauty\",\n",
    "    \"es_hyper\",\n",
    "    \"es_fashion\",\n",
    "    \"es_home\",\n",
    "    \"es_contents\",\n",
    "    \"es_travel\",\n",
    "    \"es_leisure\",\n",
    "]\n",
    "\n",
    "# One Hot Encode the newly defined mappings\n",
    "one_hot_encoder_mapping = {\n",
    "    \"category\": main_categories,\n",
    "    \"gender\": list(transactions_data.gender.unique()),\n",
    "}\n",
    "\n",
    "# Define the graph steps\n",
    "transaction_set.graph.to(\n",
    "    DateExtractor(parts=[\"hour\", \"day_of_week\"], timestamp_col=\"timestamp\")\n",
    ").to(MapValues(mapping={\"age\": {\"U\": \"0\"}}, with_original_features=True)).to(\n",
    "    OneHotEncoder(mapping=one_hot_encoder_mapping)\n",
    ")\n",
    "\n",
    "\n",
    "# Add aggregations for 2, 12, and 24 hour time windows\n",
    "transaction_set.add_aggregation(\n",
    "    name=\"amount\",\n",
    "    column=\"amount\",\n",
    "    operations=[\"avg\", \"sum\", \"count\", \"max\"],\n",
    "    windows=[\"2h\", \"12h\", \"24h\"],\n",
    "    period=\"1h\",\n",
    ")\n",
    "\n",
    "\n",
    "# Add the category aggregations over a 14 day window\n",
    "for category in main_categories:\n",
    "    transaction_set.add_aggregation(\n",
    "        name=category,\n",
    "        column=f\"category_{category}\",\n",
    "        operations=[\"sum\"],\n",
    "        windows=[\"14d\"],\n",
    "        period=\"1d\",\n",
    "    )\n",
    "\n",
    "# Add default (offline-parquet & online-nosql) targets\n",
    "transaction_set.set_targets()\n",
    "\n",
    "# Plot the pipeline so you can see the different steps\n",
    "transaction_set.plot(rankdir=\"LR\", with_targets=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Transactions - ingestion"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>amount_sum_2h</th>\n",
       "      <th>amount_sum_12h</th>\n",
       "      <th>amount_sum_24h</th>\n",
       "      <th>amount_count_2h</th>\n",
       "      <th>amount_count_12h</th>\n",
       "      <th>amount_count_24h</th>\n",
       "      <th>amount_max_2h</th>\n",
       "      <th>amount_max_12h</th>\n",
       "      <th>amount_max_24h</th>\n",
       "      <th>amount_avg_2h</th>\n",
       "      <th>...</th>\n",
       "      <th>category_es_contents</th>\n",
       "      <th>category_es_travel</th>\n",
       "      <th>category_es_leisure</th>\n",
       "      <th>amount</th>\n",
       "      <th>fraud</th>\n",
       "      <th>timestamp</th>\n",
       "      <th>target</th>\n",
       "      <th>device</th>\n",
       "      <th>timestamp_hour</th>\n",
       "      <th>timestamp_day_of_week</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>source</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>C1022153336</th>\n",
       "      <td>26.92</td>\n",
       "      <td>26.92</td>\n",
       "      <td>26.92</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>26.92</td>\n",
       "      <td>26.92</td>\n",
       "      <td>26.92</td>\n",
       "      <td>26.92</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>26.92</td>\n",
       "      <td>0</td>\n",
       "      <td>2024-09-10 06:39:33.730952000</td>\n",
       "      <td>M1823072687</td>\n",
       "      <td>33832bb8607545df97632a7ab02d69c4</td>\n",
       "      <td>6</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>C1006176917</th>\n",
       "      <td>48.22</td>\n",
       "      <td>48.22</td>\n",
       "      <td>48.22</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>48.22</td>\n",
       "      <td>48.22</td>\n",
       "      <td>48.22</td>\n",
       "      <td>48.22</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>48.22</td>\n",
       "      <td>0</td>\n",
       "      <td>2024-09-10 06:39:52.024577913</td>\n",
       "      <td>M348934600</td>\n",
       "      <td>fadd829c49e74ffa86c8da3be75ada53</td>\n",
       "      <td>6</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>C1010936270</th>\n",
       "      <td>17.56</td>\n",
       "      <td>17.56</td>\n",
       "      <td>17.56</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>17.56</td>\n",
       "      <td>17.56</td>\n",
       "      <td>17.56</td>\n",
       "      <td>17.56</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>17.56</td>\n",
       "      <td>0</td>\n",
       "      <td>2024-09-10 06:39:57.131747939</td>\n",
       "      <td>M348934600</td>\n",
       "      <td>58d0422a50bc40c89d2b4977b2f1beea</td>\n",
       "      <td>6</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>3 rows × 56 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "             amount_sum_2h  amount_sum_12h  amount_sum_24h  amount_count_2h  \\\n",
       "source                                                                        \n",
       "C1022153336          26.92           26.92           26.92              1.0   \n",
       "C1006176917          48.22           48.22           48.22              1.0   \n",
       "C1010936270          17.56           17.56           17.56              1.0   \n",
       "\n",
       "             amount_count_12h  amount_count_24h  amount_max_2h  \\\n",
       "source                                                           \n",
       "C1022153336               1.0               1.0          26.92   \n",
       "C1006176917               1.0               1.0          48.22   \n",
       "C1010936270               1.0               1.0          17.56   \n",
       "\n",
       "             amount_max_12h  amount_max_24h  amount_avg_2h  ...  \\\n",
       "source                                                      ...   \n",
       "C1022153336           26.92           26.92          26.92  ...   \n",
       "C1006176917           48.22           48.22          48.22  ...   \n",
       "C1010936270           17.56           17.56          17.56  ...   \n",
       "\n",
       "             category_es_contents  category_es_travel  category_es_leisure  \\\n",
       "source                                                                       \n",
       "C1022153336                     0                   0                    0   \n",
       "C1006176917                     0                   0                    0   \n",
       "C1010936270                     0                   0                    0   \n",
       "\n",
       "             amount  fraud                     timestamp       target  \\\n",
       "source                                                                  \n",
       "C1022153336   26.92      0 2024-09-10 06:39:33.730952000  M1823072687   \n",
       "C1006176917   48.22      0 2024-09-10 06:39:52.024577913   M348934600   \n",
       "C1010936270   17.56      0 2024-09-10 06:39:57.131747939   M348934600   \n",
       "\n",
       "                                       device  timestamp_hour  \\\n",
       "source                                                          \n",
       "C1022153336  33832bb8607545df97632a7ab02d69c4               6   \n",
       "C1006176917  fadd829c49e74ffa86c8da3be75ada53               6   \n",
       "C1010936270  58d0422a50bc40c89d2b4977b2f1beea               6   \n",
       "\n",
       "             timestamp_day_of_week  \n",
       "source                              \n",
       "C1022153336                      1  \n",
       "C1006176917                      1  \n",
       "C1010936270                      1  \n",
       "\n",
       "[3 rows x 56 columns]"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Ingest your transactions dataset through your defined pipeline\n",
    "transactions_df = transaction_set.ingest(\n",
    "    transactions_data, infer_options=fstore.InferOptions.default()\n",
    ")\n",
    "\n",
    "transactions_df.head(3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "After performing the ingestion process, you can see all of the different features that were created with the help of the UI, as shown in the image below."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "![Features Catalog - fraud prevention](../../_static/images/features-catalog-transaction.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1.2 - User events"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### User events - fetching"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>source</th>\n",
       "      <th>event</th>\n",
       "      <th>timestamp</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>C1974668487</td>\n",
       "      <td>details_change</td>\n",
       "      <td>2024-09-11 15:47:41.318821086</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>C1973547259</td>\n",
       "      <td>login</td>\n",
       "      <td>2024-09-11 18:49:15.575961508</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>C515668508</td>\n",
       "      <td>login</td>\n",
       "      <td>2024-09-11 15:22:21.857138302</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "        source           event                     timestamp\n",
       "0  C1974668487  details_change 2024-09-11 15:47:41.318821086\n",
       "1  C1973547259           login 2024-09-11 18:49:15.575961508\n",
       "2   C515668508           login 2024-09-11 15:22:21.857138302"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Fetch the user_events dataset from the server\n",
    "user_events_data = pd.read_csv(\n",
    "    \"https://s3.wasabisys.com/iguazio/data/fraud-demo-mlrun-fs-docs/events.csv\",\n",
    "    index_col=0,\n",
    "    quotechar=\"'\",\n",
    "    parse_dates=[\"timestamp\"],\n",
    ")\n",
    "\n",
    "# Adjust to the last 2 days to see the latest aggregations in the online feature vectors\n",
    "user_events_data = adjust_data_timespan(user_events_data, new_period=\"2d\")\n",
    "\n",
    "# Preview\n",
    "user_events_data.head(3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### User events - create a feature set and preprocessing pipeline\n",
    "\n",
    "Now define the events feature set.\n",
    "This is a pretty straightforward pipeline in which you only \"one hot encode\" the event categories and save the data to the default targets."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "user_events_set = fstore.FeatureSet(\n",
    "    \"events\",\n",
    "    entities=[fstore.Entity(\"source\")],\n",
    "    timestamp_key=\"timestamp\",\n",
    "    description=\"user events feature set\",\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 2.43.0 (0)\n",
       " -->\n",
       "<!-- Title: mlrun&#45;flow Pages: 1 -->\n",
       "<svg width=\"394pt\" height=\"98pt\"\n",
       " viewBox=\"0.00 0.00 393.88 98.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 94)\">\n",
       "<title>mlrun&#45;flow</title>\n",
       "<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-94 389.88,-94 389.88,4 -4,4\"/>\n",
       "<!-- _start -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>_start</title>\n",
       "<polygon fill=\"lightgrey\" stroke=\"black\" points=\"38.55,-27.05 40.7,-27.15 42.83,-27.3 44.92,-27.49 46.98,-27.74 48.99,-28.03 50.95,-28.36 52.84,-28.75 54.66,-29.18 56.4,-29.65 58.06,-30.16 59.63,-30.71 61.11,-31.31 62.49,-31.94 63.76,-32.61 64.93,-33.31 65.99,-34.04 66.93,-34.8 67.77,-35.59 68.48,-36.41 69.09,-37.25 69.58,-38.11 69.95,-38.99 70.21,-39.89 70.36,-40.8 70.4,-41.72 70.33,-42.65 70.16,-43.59 69.89,-44.53 69.53,-45.47 69.07,-46.41 68.52,-47.35 67.89,-48.28 67.18,-49.2 66.4,-50.11 65.55,-51.01 64.63,-51.89 63.65,-52.75 62.62,-53.59 61.53,-54.41 60.4,-55.2 59.23,-55.96 58.02,-56.69 56.78,-57.39 55.5,-58.06 54.2,-58.69 52.88,-59.29 51.53,-59.84 50.17,-60.35 48.79,-60.82 47.4,-61.25 46,-61.64 44.59,-61.97 43.17,-62.26 41.75,-62.51 40.32,-62.7 38.89,-62.85 37.45,-62.95 36.02,-63 34.58,-63 33.15,-62.95 31.71,-62.85 30.28,-62.7 28.85,-62.51 27.43,-62.26 26.01,-61.97 24.6,-61.64 23.2,-61.25 21.81,-60.82 20.43,-60.35 19.07,-59.84 17.72,-59.29 16.4,-58.69 15.1,-58.06 13.82,-57.39 12.58,-56.69 11.37,-55.96 10.2,-55.2 9.07,-54.41 7.98,-53.59 6.95,-52.75 5.97,-51.89 5.05,-51.01 4.2,-50.11 3.42,-49.2 2.71,-48.28 2.08,-47.35 1.53,-46.41 1.07,-45.47 0.71,-44.53 0.44,-43.59 0.27,-42.65 0.2,-41.72 0.24,-40.8 0.39,-39.89 0.65,-38.99 1.02,-38.11 1.51,-37.25 2.11,-36.41 2.83,-35.59 3.66,-34.8 4.61,-34.04 5.67,-33.31 6.84,-32.61 8.11,-31.94 9.49,-31.31 10.97,-30.71 12.54,-30.16 14.2,-29.65 15.94,-29.18 17.76,-28.75 19.65,-28.36 21.61,-28.03 23.62,-27.74 25.68,-27.49 27.77,-27.3 29.9,-27.15 32.05,-27.05 34.22,-27 36.38,-27 38.55,-27.05\"/>\n",
       "<text text-anchor=\"middle\" x=\"35.3\" y=\"-41.3\" font-family=\"Times,serif\" font-size=\"14.00\">start</text>\n",
       "</g>\n",
       "<!-- OneHotEncoder -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>OneHotEncoder</title>\n",
       "<ellipse fill=\"none\" stroke=\"black\" cx=\"191.74\" cy=\"-45\" rx=\"85.29\" ry=\"18\"/>\n",
       "<text text-anchor=\"middle\" x=\"191.74\" y=\"-41.3\" font-family=\"Times,serif\" font-size=\"14.00\">OneHotEncoder</text>\n",
       "</g>\n",
       "<!-- _start&#45;&gt;OneHotEncoder -->\n",
       "<g id=\"edge1\" class=\"edge\">\n",
       "<title>_start&#45;&gt;OneHotEncoder</title>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M69.81,-45C77.82,-45 86.77,-45 96.08,-45\"/>\n",
       "<polygon fill=\"black\" stroke=\"black\" points=\"96.29,-48.5 106.29,-45 96.29,-41.5 96.29,-48.5\"/>\n",
       "</g>\n",
       "<!-- parquet/parquet -->\n",
       "<g id=\"node3\" class=\"node\">\n",
       "<title>parquet/parquet</title>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M385.88,-86.73C385.88,-88.53 369.52,-90 349.38,-90 329.24,-90 312.88,-88.53 312.88,-86.73 312.88,-86.73 312.88,-57.27 312.88,-57.27 312.88,-55.47 329.24,-54 349.38,-54 369.52,-54 385.88,-55.47 385.88,-57.27 385.88,-57.27 385.88,-86.73 385.88,-86.73\"/>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M385.88,-86.73C385.88,-84.92 369.52,-83.45 349.38,-83.45 329.24,-83.45 312.88,-84.92 312.88,-86.73\"/>\n",
       "<text text-anchor=\"middle\" x=\"349.38\" y=\"-68.3\" font-family=\"Times,serif\" font-size=\"14.00\">parquet</text>\n",
       "</g>\n",
       "<!-- OneHotEncoder&#45;&gt;parquet/parquet -->\n",
       "<g id=\"edge2\" class=\"edge\">\n",
       "<title>OneHotEncoder&#45;&gt;parquet/parquet</title>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M258.3,-56.37C273.13,-58.94 288.57,-61.62 302.39,-64.02\"/>\n",
       "<polygon fill=\"black\" stroke=\"black\" points=\"302.15,-67.53 312.6,-65.79 303.35,-60.64 302.15,-67.53\"/>\n",
       "</g>\n",
       "<!-- nosql/nosql -->\n",
       "<g id=\"node4\" class=\"node\">\n",
       "<title>nosql/nosql</title>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M376.88,-32.73C376.88,-34.53 364.56,-36 349.38,-36 334.21,-36 321.88,-34.53 321.88,-32.73 321.88,-32.73 321.88,-3.27 321.88,-3.27 321.88,-1.47 334.21,0 349.38,0 364.56,0 376.88,-1.47 376.88,-3.27 376.88,-3.27 376.88,-32.73 376.88,-32.73\"/>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M376.88,-32.73C376.88,-30.92 364.56,-29.45 349.38,-29.45 334.21,-29.45 321.88,-30.92 321.88,-32.73\"/>\n",
       "<text text-anchor=\"middle\" x=\"349.38\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">nosql</text>\n",
       "</g>\n",
       "<!-- OneHotEncoder&#45;&gt;nosql/nosql -->\n",
       "<g id=\"edge3\" class=\"edge\">\n",
       "<title>OneHotEncoder&#45;&gt;nosql/nosql</title>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M258.3,-33.63C276.56,-30.46 295.75,-27.13 311.7,-24.36\"/>\n",
       "<polygon fill=\"black\" stroke=\"black\" points=\"312.42,-27.79 321.68,-22.63 311.22,-20.89 312.42,-27.79\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "<graphviz.graphs.Digraph at 0x7f69521194c0>"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Define and add value mapping\n",
    "events_mapping = {\"event\": list(user_events_data.event.unique())}\n",
    "\n",
    "# One Hot Encode\n",
    "user_events_set.graph.to(OneHotEncoder(mapping=events_mapping))\n",
    "\n",
    "# Add default (offline-parquet & online-nosql) targets\n",
    "user_events_set.set_targets()\n",
    "\n",
    "# Plot the pipeline so you can see the different steps\n",
    "user_events_set.plot(rankdir=\"LR\", with_targets=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### User Events - Ingestion"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>event_details_change</th>\n",
       "      <th>event_login</th>\n",
       "      <th>event_password_change</th>\n",
       "      <th>timestamp</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>source</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>C1974668487</th>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>2024-09-11 15:47:41.318821086</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>C1973547259</th>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>2024-09-11 18:49:15.575961508</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>C515668508</th>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>2024-09-11 15:22:21.857138302</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "             event_details_change  event_login  event_password_change  \\\n",
       "source                                                                  \n",
       "C1974668487                     1            0                      0   \n",
       "C1973547259                     0            1                      0   \n",
       "C515668508                      0            1                      0   \n",
       "\n",
       "                                timestamp  \n",
       "source                                     \n",
       "C1974668487 2024-09-11 15:47:41.318821086  \n",
       "C1973547259 2024-09-11 18:49:15.575961508  \n",
       "C515668508  2024-09-11 15:22:21.857138302  "
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Ingestion of your newly created events feature set\n",
    "events_df = user_events_set.ingest(user_events_data)\n",
    "events_df.head(3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 2 - Create a labels data set for model training"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Label set - create a feature set\n",
    "This feature set contains the label for the fraud demo. It is ingested directly to the default targets without any changes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "def create_labels(df):\n",
    "    labels = df[[\"fraud\", \"timestamp\"]].copy()\n",
    "    labels = labels.rename(columns={\"fraud\": \"label\"})\n",
    "    labels[\"timestamp\"] = labels[\"timestamp\"].astype(\"datetime64[ms]\")\n",
    "    labels[\"label\"] = labels[\"label\"].astype(int)\n",
    "    return labels"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 2.43.0 (0)\n",
       " -->\n",
       "<!-- Title: mlrun&#45;flow Pages: 1 -->\n",
       "<svg width=\"151pt\" height=\"193pt\"\n",
       " viewBox=\"0.00 0.00 150.99 193.25\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 189.25)\">\n",
       "<title>mlrun&#45;flow</title>\n",
       "<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-189.25 146.99,-189.25 146.99,4 -4,4\"/>\n",
       "<!-- _start -->\n",
       "<g id=\"node1\" class=\"node\">\n",
       "<title>_start</title>\n",
       "<polygon fill=\"lightgrey\" stroke=\"black\" points=\"74.74,-149.3 76.89,-149.4 79.02,-149.55 81.12,-149.74 83.18,-149.99 85.19,-150.28 87.14,-150.61 89.03,-151 90.85,-151.43 92.6,-151.9 94.26,-152.41 95.83,-152.96 97.3,-153.56 98.68,-154.19 99.96,-154.86 101.12,-155.56 102.18,-156.29 103.13,-157.05 103.96,-157.84 104.68,-158.66 105.28,-159.5 105.77,-160.36 106.14,-161.24 106.4,-162.14 106.55,-163.05 106.59,-163.97 106.53,-164.9 106.36,-165.84 106.09,-166.78 105.72,-167.72 105.26,-168.66 104.72,-169.6 104.09,-170.53 103.38,-171.45 102.59,-172.36 101.74,-173.26 100.82,-174.14 99.84,-175 98.81,-175.84 97.73,-176.66 96.6,-177.45 95.42,-178.21 94.21,-178.94 92.97,-179.64 91.7,-180.31 90.39,-180.94 89.07,-181.54 87.73,-182.09 86.36,-182.6 84.98,-183.07 83.59,-183.5 82.19,-183.89 80.78,-184.22 79.36,-184.51 77.94,-184.76 76.51,-184.95 75.08,-185.1 73.65,-185.2 72.21,-185.25 70.78,-185.25 69.34,-185.2 67.91,-185.1 66.47,-184.95 65.05,-184.76 63.62,-184.51 62.2,-184.22 60.79,-183.89 59.39,-183.5 58,-183.07 56.62,-182.6 55.26,-182.09 53.92,-181.54 52.59,-180.94 51.29,-180.31 50.02,-179.64 48.77,-178.94 47.56,-178.21 46.39,-177.45 45.26,-176.66 44.18,-175.84 43.14,-175 42.16,-174.14 41.25,-173.26 40.39,-172.36 39.61,-171.45 38.9,-170.53 38.27,-169.6 37.72,-168.66 37.27,-167.72 36.9,-166.78 36.63,-165.84 36.46,-164.9 36.39,-163.97 36.43,-163.05 36.58,-162.14 36.84,-161.24 37.22,-160.36 37.71,-159.5 38.31,-158.66 39.03,-157.84 39.86,-157.05 40.81,-156.29 41.86,-155.56 43.03,-154.86 44.3,-154.19 45.68,-153.56 47.16,-152.96 48.73,-152.41 50.39,-151.9 52.13,-151.43 53.96,-151 55.85,-150.61 57.8,-150.28 59.81,-149.99 61.87,-149.74 63.97,-149.55 66.1,-149.4 68.25,-149.3 70.41,-149.25 72.58,-149.25 74.74,-149.3\"/>\n",
       "<text text-anchor=\"middle\" x=\"71.49\" y=\"-163.55\" font-family=\"Times,serif\" font-size=\"14.00\">start</text>\n",
       "</g>\n",
       "<!-- create_labels -->\n",
       "<g id=\"node2\" class=\"node\">\n",
       "<title>create_labels</title>\n",
       "<ellipse fill=\"none\" stroke=\"black\" cx=\"71.49\" cy=\"-95.25\" rx=\"71.49\" ry=\"18\"/>\n",
       "<text text-anchor=\"middle\" x=\"71.49\" y=\"-91.55\" font-family=\"Times,serif\" font-size=\"14.00\">create_labels</text>\n",
       "</g>\n",
       "<!-- _start&#45;&gt;create_labels -->\n",
       "<g id=\"edge1\" class=\"edge\">\n",
       "<title>_start&#45;&gt;create_labels</title>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M71.49,-148.95C71.49,-141.23 71.49,-131.96 71.49,-123.36\"/>\n",
       "<polygon fill=\"black\" stroke=\"black\" points=\"74.99,-123.35 71.49,-113.35 67.99,-123.35 74.99,-123.35\"/>\n",
       "</g>\n",
       "<!-- parquet/labels -->\n",
       "<g id=\"node3\" class=\"node\">\n",
       "<title>parquet/labels</title>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M100.99,-37.6C100.99,-39.68 87.77,-41.38 71.49,-41.38 55.22,-41.38 41.99,-39.68 41.99,-37.6 41.99,-37.6 41.99,-3.65 41.99,-3.65 41.99,-1.57 55.22,0.13 71.49,0.13 87.77,0.13 100.99,-1.57 100.99,-3.65 100.99,-3.65 100.99,-37.6 100.99,-37.6\"/>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M100.99,-37.6C100.99,-35.52 87.77,-33.83 71.49,-33.83 55.22,-33.83 41.99,-35.52 41.99,-37.6\"/>\n",
       "<text text-anchor=\"start\" x=\"49.99\" y=\"-21.43\" font-family=\"Times,serif\" font-size=\"14.00\">labels</text>\n",
       "<text text-anchor=\"start\" x=\"52.49\" y=\"-12.22\" font-family=\"Times,serif\" font-size=\"8.00\">(parquet)</text>\n",
       "</g>\n",
       "<!-- create_labels&#45;&gt;parquet/labels -->\n",
       "<g id=\"edge2\" class=\"edge\">\n",
       "<title>create_labels&#45;&gt;parquet/labels</title>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M71.49,-77.04C71.49,-69.4 71.49,-60.2 71.49,-51.5\"/>\n",
       "<polygon fill=\"black\" stroke=\"black\" points=\"74.99,-51.28 71.49,-41.28 67.99,-51.28 74.99,-51.28\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "<graphviz.graphs.Digraph at 0x7f694864eb20>"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from mlrun.datastore import ParquetTarget\n",
    "import os\n",
    "\n",
    "# Define the \"labels\" feature set\n",
    "labels_set = fstore.FeatureSet(\n",
    "    \"labels\",\n",
    "    entities=[fstore.Entity(\"source\")],\n",
    "    timestamp_key=\"timestamp\",\n",
    "    description=\"training labels\",\n",
    "    engine=\"pandas\",\n",
    ")\n",
    "\n",
    "labels_set.graph.to(name=\"create_labels\", handler=create_labels)\n",
    "\n",
    "\n",
    "# specify only Parquet (offline) target since its not used for real-time\n",
    "target = ParquetTarget(\n",
    "    name=\"labels\", path=f\"v3io:///projects/{project.name}/target.parquet\"\n",
    ")\n",
    "labels_set.set_targets([target], with_defaults=False)\n",
    "labels_set.plot(with_targets=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Label set - ingestion"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>label</th>\n",
       "      <th>timestamp</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>source</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>C1022153336</th>\n",
       "      <td>0</td>\n",
       "      <td>2024-09-10 06:39:33.730</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>C1006176917</th>\n",
       "      <td>0</td>\n",
       "      <td>2024-09-10 06:39:52.024</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>C1010936270</th>\n",
       "      <td>0</td>\n",
       "      <td>2024-09-10 06:39:57.131</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "             label               timestamp\n",
       "source                                    \n",
       "C1022153336      0 2024-09-10 06:39:33.730\n",
       "C1006176917      0 2024-09-10 06:39:52.024\n",
       "C1010936270      0 2024-09-10 06:39:57.131"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Ingest the labels feature set\n",
    "labels_df = labels_set.ingest(transactions_data)\n",
    "labels_df.head(3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 3 - Deploy a real-time pipeline\n",
    "\n",
    "When dealing with real-time aggregation, it's important to be able to update these aggregations in real-time.\n",
    "For this purpose, you create live serving functions that update the online feature store of the `transactions` \n",
    "FeatureSet and `Events` FeatureSet.\n",
    "\n",
    "Using MLRun's `serving` runtime, create a nuclio function loaded with your feature set's computational graph definition\n",
    "and an `HttpSource` to define the HTTP trigger.\n",
    "\n",
    "Notice that the implementation below does not require any rewrite of the pipeline logic."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3.1 - Transactions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Transactions - deploy the feature set live endpoint"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create iguazio v3io stream and transactions push API endpoint\n",
    "transaction_stream = f\"v3io:///projects/{project.name}/streams/transaction\"\n",
    "transaction_pusher = mlrun.datastore.get_stream_pusher(transaction_stream)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[nuclio] 2024-09-12 06:43:21,453 workerAllocationMode was automatically set to 'static' because explicitAckMode is enabled\n",
      "> 2024-09-12 06:43:21,453 [warning] Setting function replicas to 1\n",
      "> 2024-09-12 06:43:21,457 [info] Starting remote function deploy\n",
      "2024-09-12 06:43:21  (info) Deploying function\n",
      "2024-09-12 06:43:21  (info) Building\n",
      "2024-09-12 06:43:21  (info) Staging files and preparing base images\n",
      "2024-09-12 06:43:21  (warn) Using user provided base image, runtime interpreter version is provided by the base image\n",
      "2024-09-12 06:43:21  (info) Building processor image\n",
      "2024-09-12 06:45:07  (info) Build complete\n",
      "2024-09-12 06:45:15  (info) Function deploy complete\n",
      "> 2024-09-12 06:45:22,959 [info] Successfully deployed function: {\"external_invocation_urls\":[\"fraud-demo-jill-transactions-ingest.default-tenant.app.vmdev94.lab.iguazeng.com/\"],\"internal_invocation_urls\":[\"nuclio-fraud-demo-jill-transactions-ingest.default-tenant.svc.cluster.local:8080\"]}\n"
     ]
    }
   ],
   "source": [
    "# Define the source stream trigger (use v3io streams)\n",
    "# define the `key` and `time` fields (extracted from the Json message).\n",
    "source = mlrun.datastore.sources.StreamSource(\n",
    "    path=transaction_stream, key_field=\"source\", time_field=\"timestamp\"\n",
    ")\n",
    "\n",
    "# Deploy the transactions feature set's ingestion service over a real-time (Nuclio) serverless function\n",
    "# you can use the run_config parameter to pass function/service specific configuration\n",
    "transaction_set_endpoint, function = transaction_set.deploy_ingestion_service(\n",
    "    source=source\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Transactions - test the feature set HTTP endpoint"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "By defining your `transactions` feature set you can now use MLRun and Storey to deploy it as a live endpoint, ready to ingest new data!\n",
    "\n",
    "Using MLRun's `serving` runtime, create a nuclio function loaded with your feature set's computational graph definition \n",
    "and an `HttpSource` to define the HTTP trigger."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'step': 135,\n",
       " 'age': '1',\n",
       " 'gender': 'M',\n",
       " 'zipcodeOri': 28007,\n",
       " 'zipMerchant': 28007,\n",
       " 'category': 'es_food',\n",
       " 'amount': 54.57,\n",
       " 'fraud': 0,\n",
       " 'timestamp': '2024-09-12 06:45:23.045196',\n",
       " 'source': 'C1014010834',\n",
       " 'target': 'M85975013',\n",
       " 'device': '26d74c1ada0048a7b6002731b15b86e1'}"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import requests\n",
    "import json\n",
    "\n",
    "# Select a sample from the dataset and serialize it to JSON\n",
    "transaction_sample = json.loads(transactions_data.sample(1).to_json(orient=\"records\"))[\n",
    "    0\n",
    "]\n",
    "transaction_sample[\"timestamp\"] = str(pd.Timestamp.now())\n",
    "transaction_sample"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'{\"id\": \"9ac1c85a-ee11-4d08-a7c0-8d2b2bf8f68b\"}'"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Post the sample to the ingestion endpoint\n",
    "requests.post(transaction_set_endpoint, json=transaction_sample).text"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3.2 - User events"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### User events - deploy the feature set live endpoint\n",
    "Deploy the events feature set's ingestion service using the feature set and all the previously defined resources."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create iguazio v3io stream and transactions push API endpoint\n",
    "events_stream = f\"v3io:///projects/{project.name}/streams/events\"\n",
    "events_pusher = mlrun.datastore.get_stream_pusher(events_stream)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[nuclio] 2024-09-12 06:45:23,248 workerAllocationMode was automatically set to 'static' because explicitAckMode is enabled\n",
      "> 2024-09-12 06:45:23,250 [warning] Setting function replicas to 1\n",
      "> 2024-09-12 06:45:23,252 [info] Starting remote function deploy\n",
      "2024-09-12 06:45:23  (info) Deploying function\n",
      "2024-09-12 06:45:23  (info) Building\n",
      "2024-09-12 06:45:24  (info) Staging files and preparing base images\n",
      "2024-09-12 06:45:24  (warn) Using user provided base image, runtime interpreter version is provided by the base image\n",
      "2024-09-12 06:45:24  (info) Building processor image\n",
      "2024-09-12 06:47:09  (info) Build complete\n",
      "2024-09-12 06:47:17  (info) Function deploy complete\n",
      "> 2024-09-12 06:47:25,826 [info] Successfully deployed function: {\"external_invocation_urls\":[\"fraud-demo-jill-events-ingest.default-tenant.app.vmdev94.lab.iguazeng.com/\"],\"internal_invocation_urls\":[\"nuclio-fraud-demo-jill-events-ingest.default-tenant.svc.cluster.local:8080\"]}\n"
     ]
    }
   ],
   "source": [
    "# Define the source stream trigger (use v3io streams)\n",
    "# define the `key` and `time` fields (extracted from the Json message).\n",
    "source = mlrun.datastore.sources.StreamSource(\n",
    "    path=events_stream, key_field=\"source\", time_field=\"timestamp\"\n",
    ")\n",
    "\n",
    "# Deploy the transactions feature set's ingestion service over a real-time (Nuclio) serverless function\n",
    "# you can use the run_config parameter to pass function/service specific configuration\n",
    "events_set_endpoint, function = user_events_set.deploy_ingestion_service(source=source)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### User Events - Test the feature set HTTP endpoint"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'source': 'C228882909',\n",
       " 'event': 'password_change',\n",
       " 'timestamp': '2024-09-12 06:47:25.897467'}"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Select a sample from the events dataset and serialize it to JSON\n",
    "user_events_sample = json.loads(user_events_data.sample(1).to_json(orient=\"records\"))[0]\n",
    "user_events_sample[\"timestamp\"] = str(pd.Timestamp.now())\n",
    "user_events_sample"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'{\"id\": \"5879fb55-587f-4e0b-a71f-3c563ab9d268\"}'"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Post the sample to the ingestion endpoint\n",
    "requests.post(events_set_endpoint, json=user_events_sample).text"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Done!\n",
    "\n",
    "You've completed Part 1 of the data-ingestion with the feature store.\n",
    "Proceed to [Part 2](02-create-training-model.ipynb) to learn how to train an ML model using the feature store data."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "mlrun-base",
   "language": "python",
   "name": "conda-env-mlrun-base-py"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.18"
  },
  "toc-showtags": false
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
